package org.intelligentsia.keystone.boot;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Command class is an utility to deal with command line stuff.
 * 
 * It aim to support java command line for Linux, Windows and Mac OS plateform.
 * 
 * @author <a href="mailto:jguibert@intelligents-ia.com" >Jerome Guibert</a>
 */
public class Command {

	/**
	 * @param parameters
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public static void run(final String... parameters) throws IOException, InterruptedException {
		// copy command and argument
		final List<String> commands = new ArrayList<String>();
		commands.add(Command.getJavaCommand());
		commands.addAll(Command.getJVMArgument());
		commands.addAll(Arrays.asList(parameters));

		// initialize builder
		final ProcessBuilder processBuilder = new ProcessBuilder(commands).redirectErrorStream(true);

		// running directory
		final String runDirectoryPath = new File(".").getAbsolutePath();
		File directory = null;
		if ((runDirectoryPath != null) && !"".equals(runDirectoryPath)) {
			directory = new File(runDirectoryPath);
			if (directory.exists()) {
				processBuilder.directory(directory);
			}
		}
		// //Waiting JAVA 7 ? :p
		// //builder.inheritIO() // E/S hérité du process Java
		processBuilder.start();
	}

	/**
	 * Utility to load arguments from command line, and extract any option starting with '--'.
	 * - load --name as <name, Boolean.TRUE>
	 * - load --name=value as <name, value>
	 * - other arguments are set as <name, name>
	 * 
	 * @param argv
	 * @return a decoded map instance
	 */
	public static Map<String, String> loadArguments(final String[] argv) {
		final Map<String, String> result = new HashMap<String, String>();
		if (argv != null) {
			for (int i = 0; i < argv.length; i++) {
				final String option = argv[i];
				if (option.startsWith("--")) {
					final int equalPos = option.indexOf('=');
					final String paramName = option.substring(2, equalPos == -1 ? option.length() : equalPos);
					if (equalPos != -1) {
						result.put(paramName, option.substring(equalPos + 1));
					} else {
						result.put(paramName, Boolean.TRUE.toString());
					}
				} else {
					result.put(option, option);
				}
			}
		}
		return result;
	}

	/**
	 * Utility to convert a map of argument as argument from command line, according this pattern '--name=value'.
	 * 
	 * @param arguments
	 * @return an array of string
	 */
	public static String[] argumentToArray(final Map<String, String> arguments) {
		final List<String> result = new ArrayList<String>();
		for (final Map.Entry<String, String> entry : arguments.entrySet()) {
			result.add("--" + entry.getKey() + "=" + entry.getValue());
		}
		return result.toArray(new String[] {});
	}

	/**
	 * Utility to load arguments from command line, and extract any option starting with '--'.
	 * - load --name as <name, Boolean.TRUE>
	 * - load --name=value as <name, value>
	 * - other arguments are set as <name, name>
	 * After, load properties found in several location of specified property name instance:
	 * - file:{name}
	 * - classpath:{name},
	 * - classpath:META-INF/{name}
	 * with this priority.
	 * 
	 * @param argv
	 * @param name
	 *            propertie base name
	 * @return a decoded map instance
	 * @throws IOException
	 *             if an error occurred when reading from one of properties file.
	 */
	public static Map<String, String> loadArguments(final String[] argv, final String name) throws IOException {
		final Map<String, String> arguments = Command.loadArguments(argv);
		Command.loadPropsFrom("file:" + name, arguments);
		Command.loadPropsFrom("META-INF/" + name, arguments);
		Command.loadPropsFrom("/" + name, arguments);
		return arguments;
	}

	/**
	 * @param args
	 * @param name
	 * @param defaultTrue
	 * @return a boolean from specified name, or default value. Accepted value for true are 'true' and 'yes' (case
	 *         ignored).
	 */
	public static Boolean getBooleanArgument(final Map<String, String> args, final String name,
			final Boolean defaultTrue) {
		final String value = args.get(name);
		if (defaultTrue) {
			return (value == null) || (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"));
		} else {
			return (value != null) && (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"));
		}
	}

	/**
	 * @param args
	 * @param name
	 * @param defaultValue
	 * @return a string value from specified name or default if value of name is null
	 */
	public static String getStringArgument(final Map<String, String> args, final String name, final String defaultValue) {
		return (args.get(name) == null ? defaultValue : args.get(name));
	}

	/**
	 * @param args
	 * @param name
	 * @param defaultValue
	 * @return an integer value from specified name or default value if null or not a number.
	 */
	public static Integer getIntegerArgument(final Map<String, String> args, final String name,
			final Integer defaultValue) {
		try {
			return Integer.parseInt(Command.getStringArgument(args, name, Integer.toString(defaultValue)));
		} catch (final NumberFormatException exception) {
			return defaultValue;
		}
	}

	/**
	 * Load propeties from specified stream and set them if the key is not ever in args.
	 * 
	 * @param name
	 *            name to load
	 * @param args
	 *            arguments
	 * @throws IOException
	 *             if an error occurred when reading from resource name.
	 */
	private static void loadPropsFrom(final String name, final Map<String, String> args) throws IOException {
		InputStream is = null;
		try {
			is = BootStrap.class.getClassLoader().getResourceAsStream(name);
			if (is != null) {
				final Properties properties = new Properties();
				properties.load(is);
				for (final Iterator<Object> i = properties.keySet().iterator(); i.hasNext();) {
					final String key = ((String) i.next()).trim();
					if (!args.containsKey(key)) {
						args.put(key, properties.getProperty(key));
					}
				}
				properties.clear();
			}
		} finally {
			// we did not used (BootStrap/delete ) for separate contract
			if (is != null) {
				try {
					is.close();
				} catch (final IOException exception) {
					Console.VERBOSE("Closing Error", exception);
				}
			}
		}
	}

	/**
	 * @return the java command according operarting system.
	 */
	private static String getJavaCommand() {
		if (OSDetector.isMac()) {
			return "open";
		}
		// use javaw if available.
		final String javaPath = new File(new File(System.getProperty("sun.boot.library.path")), "javaw.exe")
				.getAbsolutePath();
		if (new File(javaPath).exists()) {
			return javaPath;
		}
		return "java";
	}

	/**
	 * Returns the input arguments passed to the Java virtual machine and set some default.
	 * This method is used for restarting system.
	 * 
	 * @return a list of String arguments.
	 */
	private static List<String> getJVMArgument() {
		final List<String> lst = ManagementFactory.getRuntimeMXBean().getInputArguments();
		final ArrayList<String> jargs = new ArrayList<String>();

		boolean xmxset = false;
		boolean xmsset = false;
		boolean useconc = false;
		boolean minheap = false;
		boolean maxheap = false;

		for (final String h : lst) {
			if (h.contains("Xmx")) {
				xmxset = true;
				if (Runtime.getRuntime().maxMemory() < 533000000) {
					jargs.add("-Xmx512m");
					continue;
				}
			} else if (h.contains("Xms")) {
				xmsset = true;
			} else if (h.contains("XX:+useconc")) {
				useconc = true;
			} else if (h.contains("minheapfree")) {
				minheap = true;
			} else if (h.contains("maxheapfree")) {
				maxheap = true;
			}
			jargs.add(h);
		}

		if (!xmxset) {
			jargs.add("-Xmx512m");
		}

		if (OSDetector.isLinux()) { // add some default option
			if (!xmsset) {
				jargs.add("-Xms64m");
			}
			if (!useconc) {
				jargs.add("-XX:+UseConcMarkSweepGC");
			}
			if (!minheap) {
				jargs.add("-XX:MinHeapFreeRatio=0");
			}
			if (!maxheap) {
				jargs.add("-XX:MaxHeapFreeRatio=0");
			}
		}
		return jargs;
	}
}
